<template>
	<view class="e-form">
		<slot />
	</view>
</template>
<script lang="ts">
	import {
		defineComponent
	} from 'vue'
	import mpMixin from '../../libs/mixin/mpMixin'
	/**
	 * Form 表单
	 * @description 此组件一般用于表单场景，可以配置Input输入框，Select弹出框，进行表单验证等。
	 * @tutorial https://www.uviewui.com/components/form.html
	 * @property {Object}						model			当前form的需要验证字段的集合
	 * @property {Object | Function | Array}	rules			验证规则
	 * @property {Array}						errorType		错误的提示方式，数组形式，见上方说明 ( 默认 ['none', 'toast'] )
	 * @property {Boolean}						borderBottom	是否显示表单域的下划线边框   ( 默认 true ）
	 * @property {String}						labelPosition	表单域提示文字的位置，left-左侧，top-上方 ( 默认 'left' ）
	 * @property {String | Number}				labelWidth		提示文字的宽度，单位px  ( 默认 45 ）
	 * @property {String}						labelAlign		lable字体的对齐方式   ( 默认 'left' ）
	 * @property {Object}						labelStyle		lable的样式，对象形式
	 * @property {String}						inputAlign		表单内输入框的对齐方式   ( 默认 'left' ）
	 * @value left
	 * @value center
	 * @value right
	 * @example <e--form labelPosition="left" :model="model1" :rules="rules" ref="form1"></e--form>
	 */
	
	export default defineComponent({
		name: 'e-form',
		mixins: [mpMixin]
	})
</script>
<script lang="ts" setup>
	import {
		reactive,
		watch,
		nextTick,
		toRefs,
		onMounted,
		provide
	} from 'vue'
	import {
		formProps,
		formEmits
	} from './props'
	import {
		useFormLabelWidth,
		filterFields
	} from './util'
	import {
		formContextKey,
		type FormItemContext,
		type FormContext,
		type FormItemRule
	} from '../../libs/tokens'
	import {
		useDefaultProps
	} from '../../libs/hooks'
	import type {
		FormRules
	} from "../../libs/tokens/form";
	import {
		deepClone,
		getProperty,
		castArray,
		isArray,
		isObj,
		toast
	} from '../../libs/utils'
	import Schema, {
		type ValidateError
	} from '../../libs/utils/async-validator'
	import componentsProps from "../../libs/config/componentsProps";
	import type {
		 FormProps
	} from "../../libs/config/propsType";

	// 去除警告信息
	Schema.warning = function () { }

	const {
		form: formDefaultProps
	} = toRefs(componentsProps)


	const _props = defineProps(formProps)

	const props = useDefaultProps<FormProps>(_props, formDefaultProps);




	const emit = defineEmits(formEmits)
	// const defaultPropsKey = 'form'
	let formRules : FormRules = {}
	// 规则校验器
	// let validator = {}
	// 原始的model快照，用于resetFields方法重置表单时使用
	let originalModel : Record<string, any> = {}
	// // 手动设置校验的规则，如果规则中有函数的话，微信小程序中会过滤掉，所以只能手动调用设置规则
	const setRules = (rules ?: FormRules) => {
		// 判断是否有规则
		if (!isObj(rules)) return
		formRules = rules
		// 重新将规则赋予Validator
		// validator = new Schema(rules)
	}
	const children : FormItemContext[] = []

	const addField : FormContext['addField'] = (field) => {
		children.push(field)
	}

	const removeField : FormContext['removeField'] = (field) => {
		let index = children.indexOf(field)
		if (index > -1) {
			children.splice(index, 1)
		}
	}

	// 监听model的初始值作为重置表单的快照
	watch(
		() => props.value.model,
		newValue => {
			if (!originalModel) {
				originalModel = deepClone(newValue as object)
			}
		}, {
		immediate: true
	}
	)

	// 清空所有e-form-item组件的内容，本质上是调用了e-form-item组件中的resetField()方法
	const resetFields = () => {
		resetModel()
	}
	// 重置model为初始值的快照
	const resetModel : FormContext['resetFields'] = (properties = []) => {
		// 历遍所有e-form-item，根据其prop属性，还原model的原始快照
		filterFields(children, properties).forEach((field) => field.resetField())
	}
	// 清空校验结果
	const clearValidate : FormContext['clearValidate'] = (props = []) => {
		// 清除e-form-item的校验结果信息
		filterFields(children, props).forEach((field) => field.clearValidate())
	}
	// 对部分表单字段进行校验
	const validateField = async (value : string[], callback : (errors : ValidateError[]) => void, event ?: string) => {
		// $nextTick是必须的，否则model的变更，可能会延后于此方法的执行
		nextTick(() => {
			// 校验错误信息，返回给回调方法，用于存放所有form-item的错误信息
			const errorsRes : ValidateError[] = []
			// 历遍children所有子form-item
			children.map(child => {
				// 用于存放form-item的错误信息
				const childErrors : ValidateError[] = []
				if (child.props.prop && value.includes(child.props.prop)) {
					// 获取对应的属性，通过类似'a.b.c'的形式
					const propertyVal = getProperty(props.value.model, child.props.prop)
					// 属性链数组
					const propertyChain = child.props.prop.split('.')
					const propertyName = propertyChain[propertyChain.length - 1]

					const rule = formRules[child.props.prop]
					// 如果不存在对应的规则，直接返回，否则校验器会报错
					if (!rule) return
					// rule为数组形式
					const rules = castArray<FormItemRule>(rule)

					// 对rules数组进行校验
					for (let i = 0; i < rules.length; i++) {
						const ruleItem = rules[i]
						// 将e-form-item的触发器转为数组形式
						const trigger = castArray(ruleItem.trigger)
						// 如果是有传入触发事件，但是此form-item却没有配置此触发器的话，不执行校验操作
						if (event && !trigger.includes(event)) continue
						// 实例化校验对象，传入构造规则
						const validator = new Schema({
							[propertyName]: ruleItem
						})
						validator.validate({
							[propertyName]: propertyVal
						},
							(errors, fields) => {
								if (isArray(errors)) {
									errorsRes.push(...errors)
									childErrors.push(...errors)
								}
								child.message = childErrors[0]?.message ?? ''
							}
						)
					}
				}
			})
			// 执行回调函数
			typeof callback === 'function' && callback(errorsRes)
		})
	}
	// 校验全部数据
	const validate = ():Promise<boolean> => {
		return new Promise((resolve, reject) => {
			// $nextTick是必须的，否则model的变更，可能会延后于validate方法
			nextTick(() => {
				// 获取所有form-item的prop，交给validateField方法进行校验
				const formItemProps : string[] = []
				children.forEach(item => {
					if (item.props.prop) {
						formItemProps.push(item.props.prop)
					}
				})
				validateField(formItemProps, errors => {
					if (errors.length) {
						// 如果错误提示方式为toast，则进行提示
						props.value.errorType === 'toast' && toast(errors[0].message)
						reject(errors)
					} else {
						resolve(true)
					}
				})
			})
		})
	}

	onMounted(() => {
		setRules(props.value.rules as FormRules)
	})

	provide(formContextKey, reactive({
		...useFormLabelWidth(),
		props,
		originalModel,
		emit,
		addField,
		removeField,
		resetFields,
		clearValidate,
		validateField
	}))
	defineExpose({
		/** @description validate form */
		validate,
		/** @description validate form field */
		validateField,
		/** @description reset fields */
		resetFields,
		/** @description clear validation status */
		clearValidate,
		/** @description set rules */
		setRules
	})
</script>

<style lang="scss" scoped>

</style>